package com.twu.biblioteca;

import java.lang.reflect.*;
import java.util.*;


interface BibliotecaCommand {
    Map runCommand();
}


public class Biblioteca {
    private ArrayList<Book> availableBooks = new ArrayList<Book>();
    private ArrayList<Movie> availableMovies = new ArrayList<Movie>();
    private final String[] availableMenuOptions  = new String[] {"List Books", "Return Book", "List Movies", "Return Movie", "User Info"};
    private ArrayList<Book> rentedBooks = new ArrayList<Book>();
    private ArrayList<Movie> rentedMovies = new ArrayList<Movie>();
    private final Map<String, BibliotecaCommand> methodMap = new HashMap<String, BibliotecaCommand>();
    private final Map<String, Map> submenuMasterMap = new HashMap<String, Map>();
    private final UserDatabase database = new UserDatabase();
    private User currentUser;

//INSTANCE SETUP METHODS
    public Biblioteca() {
        String[] availableBooksTitles  = new String[] {"Category wise tutorials - J2EE", "JAAS in Action", "Thinking in Java", "Sun's Java Tutorials"};
        String[] availableMoviesTitles  = new String[] {"Jurassic Park", "The Social Network", "Pirates of Silicon Valley"};
        String[] availableMoviesDirectors  = new String[] {"Steven Spielberg", "David Fincher", "Martyn Burke"};
        String[] availableMoviesYears  = new String[] {"1993", "2010", "1999"};
        String[] availableMoviesRatings  = new String[] {"UNRATED", "8", "7"};

        initializeBooks(availableBooksTitles);
        initializeMovies(availableMoviesTitles, availableMoviesDirectors, availableMoviesYears, availableMoviesRatings);

        fillSubmenuMaps();
        fillAvailableMainMethodsMap();
    }

    public Map getSubmenuMapInfo(String submenuName) {
        return submenuMasterMap.get(submenuName);
    }

    public String welcome() {
        return "WELCOME TO THE BIBLIOTECA APP";
    }

    public void fillAvailableMainMethodsMap(){
        methodMap.put("List Books", new BibliotecaCommand() {
            public Map runCommand() { return listBooks(); };
        });
        methodMap.put("Return Book", new BibliotecaCommand() {
            public Map runCommand() { return returnBookMenu(); };
        });
        methodMap.put("List Movies", new BibliotecaCommand() {
            public Map runCommand() { return listMovies(); };
        });
        methodMap.put("Return Movie", new BibliotecaCommand() {
            public Map runCommand() { return returnMovieMenu(); };
        });
        methodMap.put("User Info", new BibliotecaCommand() {
            public Map runCommand() { return listUserInfo(); };
        });
    }

    public void fillSubmenuMaps() {
        fillHeaderMap();
        fillPromptMap();
        fillInvalidMap();
    }

//LOGIN AND USER INFO METHODS
    public String loginUser(String libraryNumber, String password) {
        Map<String, User> returnedUserInformation = database.validate(libraryNumber, password);
        String answer = (String) returnedUserInformation.keySet().toArray()[0];
        User user = returnedUserInformation.get(answer);

        if (user != null) {
            setCurrentUser(user);
        }

        return answer;
    }

    public Map listUserInfo() {
        Map<String, String[]> userInfoReturnValue = new HashMap<String, String[]>();
        String[] submenuName = {"User Info"};
        String[] userInfo = {"NAME: "+currentUser.getName(), "EMAIL: "+currentUser.getEmail(), "PHONE: "+currentUser.getPhone()};

        return fillReturnMap(userInfoReturnValue, submenuName, userInfo);
    }

    public String changeUserInfo(int numberOfInfo) {
        int index = numberOfInfo - 1;

        return "Information can not yet be changed in this release.";
    }

//MENU METHODS
    public String[] listMainMenu() {
        return availableMenuOptions;
    }

    public Map chooseMenuOption(int menuNumber) {
        int index = menuNumber - 1;
        Map menuOptions = new HashMap();

        if(index >= availableMenuOptions.length || index < 0) {
            String[] submenuName = {"Invalid"};
            String[] submenuData = {"Select a valid option!"};
            menuOptions = fillReturnMap(menuOptions, submenuName, submenuData);
        } else {
            menuOptions = methodMap.get(availableMenuOptions[index]).runCommand();
        }

        return menuOptions;
    }

//RENT BOOK METHODS
    public Map listBooks() {
        return listMedia(availableBooks, "List Books");
    }

    public String checkoutBook(int bookNumber) {
        int index = bookNumber - 1;

        String returnValue = checkoutMedia(index, "That book is not available.", availableBooks);
        updateRentedBooksArray();

        return returnValue;
    }

//RETURN BOOK METHODS
    public Map returnBookMenu() {
        updateRentedBooksArray();

        return returnMediaMenu(rentedBooks, "Return Book");
    }

    public String returnBook(int bookNumber) {
        return returnMedia(rentedBooks, "That is not a valid book to return.", bookNumber);
    }

//RENT MOVIE METHODS
    public Map listMovies() {
        return listMedia(availableMovies, "List Movies");
    }

    public String checkoutMovie(int movieNumber) {
        int index = movieNumber - 1;

        String returnValue = checkoutMedia(index, "That movie is not available.", availableMovies);
        updateRentedMoviesArray();

        return returnValue;
    }

//RETURN MOVIE METHODS
    public Map returnMovieMenu() {
        updateRentedMoviesArray();

        return returnMediaMenu(rentedMovies, "Return Movie");
    }

    public String returnMovie(int movieNumber) {
        return returnMedia(rentedMovies, "That is not a valid movie to return.", movieNumber);
    }

//GENERAL RENT MEDIA METHODS
    public Map listMedia(ArrayList availableMedia, String submenuStringName) {
        int numberOfMedia = availableMedia.size();
        String[] mediaTitles = new String[numberOfMedia];
        Map<String, String[]> listMediaReturnValue = new HashMap<String, String[]>();
        String[] submenuName = {submenuStringName};

        for(int i=0; i < availableMedia.size(); i++){
            Media currentMedia = (Media) availableMedia.get(i);
            mediaTitles[i] = currentMedia.mediaTitles();
        }

        return fillReturnMap(listMediaReturnValue, submenuName, mediaTitles);
    }

    public String checkoutMedia(int index, String returnValue, ArrayList availableMedia) {
        if(hasArrayIndex(index, availableMedia.size())) {
            Media rentedMedia = (Media) availableMedia.get(index);
            if(!rentedMedia.isRented()){
                rentedMedia.rent(currentUser);
                returnValue = rentedMedia.checkoutReturnValue();
            }
        }

        updateRentedBooksArray();

        return returnValue;
    }

    public Map returnMediaMenu(ArrayList rentedMediaArrayList, String returnString) {
        String[] rentedMediaArray = {};
        Map<String, String[]> rentedMediaReturnValue = new HashMap<String, String[]>();
        String[] submenuName = {returnString};

        if (rentedMediaArrayList.size() != 0) {
            rentedMediaArray = new String[rentedMediaArrayList.size()];

            for(int i=0; i < rentedMediaArrayList.size(); i++){
                Media rentedMedia =  (Media) rentedMediaArrayList.get(i);
                String renter = rentedMedia.getRenter() == currentUser ? "YOU" : rentedMedia.getRenter().getName();
                rentedMediaArray[i] = rentedMedia.getTitle()+" [RENTED BY "+renter+"]";
            }
        }

        return fillReturnMap(rentedMediaReturnValue, submenuName, rentedMediaArray);
    }

    public String returnMedia(ArrayList rentedMediaArray, String returnString, int mediaNumber) {
        int index = mediaNumber - 1;
        String returnValue = returnString;

        if(!rentedMediaArray.isEmpty() && hasArrayIndex(index, rentedMediaArray.size())) {
            Media rentedMedia = (Media) rentedMediaArray.get(index);
            if(rentedMedia.isRented() && rentedMedia.getRenter() == currentUser){
                rentedMedia.giveBack();
                returnValue = rentedMedia.returnMediaValue();
            }
        }

        return returnValue;
    }

//HELPER METHODS
//MEDIA INSTANTIATION RELATED METHODS
    private void initializeBooks(String[] availableBooksTitles) {
        for(String title : availableBooksTitles){
            Book newBook = new Book(title);
            availableBooks.add(newBook);
        }
    }

    private void initializeMovies(String[] availableMoviesTitles, String[] availableMoviesDirectors, String[] availableMoviesYears, String[] availableMoviesRatings) {
        for(int i = 0; i < availableMoviesTitles.length; i++) {
            String title = availableMoviesTitles[i];
            String director = availableMoviesDirectors[i];
            String year = availableMoviesYears[i];
            String rating = availableMoviesRatings[i];

            Movie newMovie = new Movie(title, director, year, rating);
            availableMovies.add(newMovie);
        }
    }

//MAP INSTANTIATION RELATED METHODS
    private void fillHeaderMap() {
        Map<String, String> submenuHeaderMap = new HashMap<String, String>();

        submenuHeaderMap.put("List Books", "LIST BOOKS SUBMENU - CHOSE [NUMBER] TO RENT BOOK");
        submenuHeaderMap.put("Return Book", "RETURN BOOK SUBMENU - CHOSE [NUMBER] TO RETURN BOOK");
        submenuHeaderMap.put("List Movies", "LIST MOVIES SUBMENU - CHOSE [NUMBER] TO RENT MOVIE");
        submenuHeaderMap.put("Return Movie", "RETURN MOVIE SUBMENU - CHOSE [NUMBER] TO RETURN MOVIE");
        submenuHeaderMap.put("User Info", "USER INFO SUBMENU - CHOSE [NUMBER] TO CHANGE INFO");
        submenuMasterMap.put("Header", submenuHeaderMap);
    }

    private void fillPromptMap() {
        Map<String, String> submenuPromptMap = new HashMap<String, String>();

        submenuPromptMap.put("List Books", "Rent book number: ");
        submenuPromptMap.put("Return Book", "Return book number: ");
        submenuPromptMap.put("List Movies", "Rent movie number: ");
        submenuPromptMap.put("Return Movie", "Return movie number: ");
        submenuPromptMap.put("User Info", "Change user info at number:");
        submenuMasterMap.put("Prompt", submenuPromptMap);
    }

    private void fillInvalidMap() {
        Map<String, String> submenuInvalidAnswerMap = new HashMap<String, String>();

        submenuInvalidAnswerMap.put("List Books", "That book is not available.");
        submenuInvalidAnswerMap.put("Return Book", "That is not a valid book to return.");
        submenuInvalidAnswerMap.put("List Movies", "That movie is not available.");
        submenuInvalidAnswerMap.put("Return Movie", "That is not a valid movie to return.");
        submenuInvalidAnswerMap.put("User Info", "This is not a valid information to change.");
        submenuMasterMap.put("Invalid", submenuInvalidAnswerMap);
    }

    private Map fillReturnMap(Map mapToBeFilled, String[] submenuName, String[] data) {
        mapToBeFilled.put("Submenu", submenuName);
        mapToBeFilled.put("Data", data);

        return mapToBeFilled;
    }


//USER RELATED HELPER METHODS
    private void setCurrentUser(User currentUserToBeSet) {
        currentUser = currentUserToBeSet;
    }

//OTHER HELPER METHODS
    private void updateRentedBooksArray(){
        rentedBooks = new ArrayList<Book>();

        for(Book book : availableBooks) {
            if(book.isRented()) {
                rentedBooks.add(book);
            }
        }
    }

    private void updateRentedMoviesArray(){
        rentedMovies = new ArrayList<Movie>();

        for(Movie movie : availableMovies) {
            if(movie.isRented()) {
                rentedMovies.add(movie);
            }
        }
    }

    private Boolean hasArrayIndex(int index, int length){
        Boolean answer = false;

        if (index < length && index >= 0){
            answer = true;
        }

        return answer;
    }
}